# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools

from coin.base.datetime_util import to_datetime
from coin.exchange.base.tick import BitfinexTickPrice
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.okex.kr_rest.product import OkexProduct
from coin.strategy.mm.simple_sim import result_util
from coin.strategy.mm.simple_sim.strategy.pass_agg import PassAggSimStrategy


def get_products(from_ts):
  return [OkexProduct.FromStr('BTC-USDT'), BitfinexProduct.FromStr('BTC-USDT')]


def get_machines():
  return ['feed-3.eu-west-1', 'feed-6.eu-west-1']


def get_time_ranges():
  ranges = []
  cur_dt = datetime.datetime(2018, 5, 1, 0, 0, 0)
  end_dt = datetime.datetime(2018, 5, 15, 0, 0, 0)
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def linear_sell_edge(edge, close_edge, max_pos, pos):
  if pos <= 0.:
    return edge
  elif pos >= max_pos:
    return close_edge
  else:
    p = (pos / float(max_pos))
    return (p * close_edge + (1 - p) * edge)


def linear_buy_edge(edge, close_edge, max_pos, pos):
  if pos >= 0.:
    return edge
  elif pos <= -max_pos:
    return close_edge
  else:
    p = (-pos / float(max_pos))
    return (p * close_edge + (1 - p) * edge)


def get_strategy(from_ts, to_ts):
  products = get_products(from_ts)
  ref_product = products[0]
  ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_price(10000.)[1][-1][0]),
                                 (lambda book: book.get_notional_bids_by_price(10000.)[1][-1][0]))

  pass_product = products[1]
  pass_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_price(10000.)[1][-1][0]),
                                  (lambda book: book.get_notional_bids_by_price(10000.)[1][-1][0]))

  NS_PER_SECOND = (10**9)
  NS_PER_MINUTE = 60 * NS_PER_SECOND

  strategy_list = []

  for basis_ma_window in [5, 10, 15]:
    for edge_bp in [6, 8, 10, 12, 14, 16, 18]:
      for close_edge_bp in [edge_bp]:
        for agg_edge in [None, (edge_bp + 8.5) / 10000.]:
          stack = 5
          lot_size = 0.1
          pricing_param = {
              'basis_ma_window': basis_ma_window * NS_PER_MINUTE,
              'tick_1': 0.01,
              'tick_2': BitfinexTickPrice(),
              'book_askt_func_1': ref_product_true_book_funcs[0],
              'book_bidt_func_1': ref_product_true_book_funcs[1],
              'book_askt_func_2': pass_product_true_book_funcs[0],
              'book_bidt_func_2': pass_product_true_book_funcs[1]
          }

          executor_param = {
              'passive_param': {  # Bitfinex
                  'lot_size': lot_size,
                  'min_pos': -lot_size * stack,
                  'max_pos': lot_size * stack,
                  'maker_fee': 0. / 10000.,
                  'taker_fee': 10. / 10000.,
                  'post_only': True,
                  'trade_qty_func': lambda trade: trade.qty
              },
              'aggression_param': {  # Okex
                  'max_qty': lot_size * 3,
                  'maker_fee': 2. / 10000.,
                  'taker_fee': 4. / 10000.,
                  'trade_qty_func': lambda trade: trade.qty
              },
              'net_pos_allowance': lot_size * 2,  # TODO(inkyu): Fill me
          }

          name = '%02dm.%02dbp.%02dbp.%02dstack.%s' % (basis_ma_window,
                                                       edge_bp,
                                                       close_edge_bp,
                                                       stack,
                                                       to_datetime(from_ts).strftime('%Y%m%d'))

          strategy = PassAggSimStrategy(pass_product,
                                        ref_product,
                                        functools.partial(linear_sell_edge,
                                                          edge_bp / 10000.,
                                                          close_edge_bp / 10000.,
                                                          1.),
                                        functools.partial(linear_buy_edge,
                                                          edge_bp / 10000.,
                                                          close_edge_bp / 10000.,
                                                          1.),
                                        pricing_param,
                                        executor_param,
                                        trade_after=basis_ma_window,
                                        name=name,
                                        feature_filepath=('out/feature.%s.csv' % name),
                                        fill_filepath=('out/fill.%s.csv' % name))
          strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  return {'name': strategy.name, **strategy.get_summary()}


def aggregate_result(results):
  return result_util.aggregate_sim_result(results)
